// VideoPacketReader.hpp — 服务器端：拉流→把“视频包 + 流信息”交给回调
// 版本：v6.1.0
//
// 主要职责：
//   1) 使用 FFmpeg 打开输入源（文件/RTSP/RTMP/HTTP/HTTPS/UDP…）；
//   2) 仅对【视频流】的 AVPacket 触发回调（音频/字幕包自动丢弃）；
//   3) 回调前对 H264/HEVC 自动做 AVCC→Annex-B（若需要）；
//   4) 回调前尽量“规范化” pts/dts/duration，并保证 pts“尽量单调”；
//   5) 同步读：回调不返回，拉流不会继续（如何使用回调由上层决定）；
//   6) 提供统计与幂等清理；可通过 RequestStop 中断阻塞读取；
//
// 特别说明：本文件在每个关键 FFmpeg API 处都加入了“这个函数是做什么”的注释。
//           便于快速理解 FFmpeg 的调用意图与失败路径。

#ifndef VIDEO_PACKET_READER_HPP
#define VIDEO_PACKET_READER_HPP

#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif

extern "C"
{
#include <libavutil/avutil.h>     // 基础类型/错误码/工具
#include <libavcodec/avcodec.h>   // 编解码器枚举等
#include <libavformat/avformat.h> // 容器/IO
#include <libavutil/time.h>       // 时间函数
#include <libavutil/imgutils.h>   // 图像工具
#include <libavutil/pixdesc.h>    // 像素格式描述
#include <libavcodec/bsf.h>       // 比特流过滤器（Annex-B 转换）
}

#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

namespace media_types
{
    //========================== 打开选项 ==========================
    /**
     * @brief 打开/网络/探测参数
     *
     */
    struct NetworkOptions
    {
        int timeout_seconds;        ///< 【rw_timeout】整体 IO 超时（秒），控制“连接/读取阻塞”的最大等待。
        int socket_timeout_seconds; ///< 【stimeout】socket 层超时（秒），TCP 场景更灵敏。
        int max_delay_ms;           ///< RTP/RTSP 最大缓存延迟（毫秒）。
        std::string rtsp_transport; ///< RTSP 传输协议："tcp"/"udp"；弱网推荐 "tcp"。
        bool enable_reconnect;      ///< HTTP(S) 断线自动重连。
        int probe_size_bytes;       ///< avformat_open_input 探测读取的最大字节数（越大越稳，但首帧更慢）。
        int analyze_duration_ms;    ///< avformat_find_stream_info 分析时长（毫秒）。

        NetworkOptions()
            : timeout_seconds(10),
              socket_timeout_seconds(5),
              max_delay_ms(500),
              rtsp_transport("tcp"),
              enable_reconnect(true),
              probe_size_bytes(1 << 20),
              analyze_duration_ms(1500) {}
    };

    //========================== 输出服务端打开的流信息 ==========================
    struct StreamInfo
    {
        std::string url;       ///< 源地址
        int stream_index = -1; ///< 视频流索引（>=0 有效）

        double duration = -1.0;    ///< 容器报告的总时长（秒）；直播/未知为负
        int64_t total_frames = -1; ///< 容器报告的总帧数；未知为 -1

        AVCodecID codec_id = AV_CODEC_ID_NONE; ///< 编解码器ID；客户端据此找解码器
        std::string codec_name;                ///< 编解码器短名（"h264"/"hevc"）
        std::string codec_long_name;           ///< 编解码器长名（可读）

        std::vector<uint8_t> extradata; ///< 重要：H26x 的 SPS/PPS/VPS 等配置

        int width = 0;                           ///< 初始宽（可能为 0）
        int height = 0;                          ///< 初始高（可能为 0）
        AVPixelFormat pix_fmt = AV_PIX_FMT_NONE; ///< 容器标注像素格式（压缩流多为 NONE）
        std::string pix_fmt_name;                ///< 像素格式名（可读）

        AVRational time_base{1, 1};  ///< 时间基；PTS→秒 = pts * num / den
        AVRational frame_rate{0, 1}; ///< 平均帧率；未知 0/1
        double fps = 0.0;            ///< frame_rate 的 double 版本；未知 0
        int64_t bit_rate = 0;        ///< 比特率（bit/s）；未知 0

        void Print(const std::string &prefix = "") const
        {
            std::cout << prefix << "========== 视频流信息 ==========\n";
            std::cout << prefix << "源: " << url << "\n";
            std::cout << prefix << "流索引: " << stream_index << "\n";
            std::cout << prefix << "时长: " << (duration > 0 ? std::to_string(duration) + " 秒" : "未知") << "\n";
            std::cout << prefix << "总帧数: " << (total_frames > 0 ? std::to_string(total_frames) : "未知") << "\n";
            std::cout << prefix << "编解码器: " << (codec_name.empty() ? "unknown" : codec_name)
                      << " (" << (codec_long_name.empty() ? "Unknown" : codec_long_name) << ")\n";
            std::cout << prefix << "分辨率: " << (width > 0 ? std::to_string(width) : "?") << "x"
                      << (height > 0 ? std::to_string(height) : "?") << "\n";
            std::cout << prefix << "像素格式: " << (pix_fmt_name.empty() ? "未知" : pix_fmt_name) << "\n";
            std::cout << prefix << "帧率: " << (fps > 0 ? std::to_string(fps) : "未知") << " fps\n";
            std::cout << prefix << "比特率: " << (bit_rate > 0 ? std::to_string(bit_rate / 1000) + " kbps" : "未知") << "\n";
            std::cout << prefix << "时间基: " << time_base.num << "/" << time_base.den << "\n";
            std::cout << prefix << "Extradata: " << extradata.size() << " 字节\n";
            std::cout << prefix << "================================\n";
        }
    };

    //========================== 服务端内部自我监控用的统计模块 ==========================
    struct Statistics
    {
        int64_t total_packets = 0;       ///< 所有容器包（含音频/字幕）
        int64_t video_packets = 0;       ///< 视频包
        int64_t total_bytes = 0;         ///< 累计字节
        double elapsed_seconds = 0.0;    ///< 累计时长（秒）
        double packets_per_second = 0.0; ///< 视频包速率
        double mbps = 0.0;               ///< 吞吐（兆比特/秒）

        void Print(const std::string &prefix = "") const
        {
            std::cout << prefix << "========== 读取统计 ==========\n";
            std::cout << prefix << "总Packets(含音频/字幕): " << total_packets << "\n";
            std::cout << prefix << "视频Packets: " << video_packets << "\n";
            std::cout << prefix << "总字节: " << total_bytes << " (" << total_bytes / 1024 / 1024 << " MB)\n";
            std::cout << prefix << "用时: " << elapsed_seconds << " 秒\n";
            std::cout << prefix << "视频包速率: " << packets_per_second << " pkt/s\n";
            std::cout << prefix << "吞吐率: " << mbps << " Mbps\n";
            std::cout << prefix << "=============================\n";
        }
    };
} // namespace VideoPacketReader

class VideoPacketReader
{
public:
    //========================== 回调类型 ==========================
    /**
     * @brief 视频包回调函数类型（PacketCallback）
     * 当 VideoPacketReader 从输入源成功读取到一帧视频数据时，将通过该回调通知上层。上层可在回调中将 AVPacket 的内容
     *
     * @param packet
     *     指向 FFmpeg 原始压缩包的指针（AVPacket）。其中包含当前帧的压缩数据、时间戳、关键帧标志等信息。
     *     注意：此指针仅在回调执行期间有效，回调返回后 Reader 会释放该 AVPacket，
     *
     * @param info
     *     对应输入流的固定参数信息（StreamInfo）。包含编解码器 ID、宽高、帧率、时间基、extradata 等元信息。
     *
     * @note
     *     回调由拉流线程触发，内部不应执行耗时操作。建议在回调内仅做数据拷贝和队列入队操作。
     */
    using PacketCallback = std::function<void(const AVPacket *packet, const media_types::StreamInfo &info)>;

    /**
     * @brief 错误回调函数类型（ErrorCallback）
     *
     * 当 VideoPacketReader 在打开、读取或解析过程中出现错误时，将通过该回调报告错误状态和说明信息。
     *
     * @param err
     *     FFmpeg 返回的错误码（通常为负值），例如：
     *     - `AVERROR_EOF`   ：已读到流末尾
     *     - `AVERROR_EXIT`  ：被外部请求停止
     *     - `AVERROR(EAGAIN)`：暂时无数据（可忽略）
     *     - `AVERROR(ETIMEDOUT)`：连接超时
     *
     * @param msg
     *     错误说明文本（由 Reader 生成的可读字符串）。通常包含错误来源（函数名）及 FFmpeg 错误描述。
     *
     * @note
     *     该回调可能在任何阶段触发，包括初始化和读取过程。上层应避免在回调内直接抛异常或阻塞当前线程。
     */
    using ErrorCallback = std::function<void(int err, const std::string &msg)>;

public:
    //========================== 全局初始化（可选） ==========================

    /**
     * @brief 初始化 FFmpeg 网络子系统（全局一次性）
     *
     * FFmpeg 的网络模块（RTSP、HTTP、RTMP 等）在使用前需要通过 avformat_network_init() 初始化。本函数对其进行一次性封装，
     * 使用 `std::call_once` 确保在整个进程生命周期中只调用一次。
     *
     * @note
     * 只需在进程启动阶段调用一次；后续多次调用不会重复初始化。通常由 StreamBus::InitFFmpegNetwork() 间接调用。
     * 初始化后方可使用网络协议打开媒体源（如 RTSP/RTMP/HTTP）。
     */
    static void GlobalInit()
    {
        // avformat_network_init()：初始化 FFmpeg 的网络子系统（只需进程内调用一次）
        static std::once_flag f;
        std::call_once(f, []
                       {
            avformat_network_init();
            std::cout << "[VideoPacketReader] FFmpeg 网络初始化\n"; });
    }

    /**
     * @brief 清理 FFmpeg 网络子系统（与 GlobalInit 成对）
     * 调用 FFmpeg 的 avformat_network_deinit() 释放全局网络资源。该函数为可选调用，用于程序退出时的善后处理。
     *
     * @note
     * 若进程即将结束，可不必显式调用，FFmpeg 会在静态析构中清理。但在需要反复加载/卸载 FFmpeg 模块的场景中（如插件或动态库），建议显式调用以确保资源完整释放。
     */
    static void GlobalCleanup()
    {
        // avformat_network_deinit()：与上面成对，用于释放网络资源（可选）
        avformat_network_deinit();
        std::cout << "[VideoPacketReader] FFmpeg 网络清理\n";
    }

    //========================== 构造/析构 ==========================

    /**
     * @brief 构造函数
     *
     * 初始化内部状态并调用 GlobalInit() 确保 FFmpeg 网络系统已初始化。同时初始化计数器、标志位及时间起点，用于后续统计。
     *
     * @note
     * 默认不打开任何输入源，需后续调用 Open(url)。构造时自动记录开始时间，用于统计 elapsed_seconds。
     */
    VideoPacketReader()
        : fmt_(nullptr), pkt_(nullptr), v_idx_(-1), bsf_(nullptr),
          opened_(false), total_pkts_(0), video_pkts_(0), total_bytes_(0),
          last_pts_(AV_NOPTS_VALUE), stop_flag_(false)
    {
        GlobalInit();
        start_ = std::chrono::steady_clock::now();
    }

    /**
     * @brief 析构函数
     * 在对象销毁时自动关闭输入流并打印读取统计信息。
     *
     * @note
     * 若对象仍处于打开状态，会先调用 PrintStatistics() 输出统计结果。随后调用 Close() 释放所有资源（AVFormatContext、AVPacket、比特流过滤器等）。
     */
    ~VideoPacketReader()
    {
        if (opened_)
            PrintStatistics();
        Close();
    }

    // 禁用拷贝构造函数、赋值运算符
    VideoPacketReader(const VideoPacketReader &) = delete;
    VideoPacketReader &operator=(const VideoPacketReader &) = delete;

    //========================== 核心 API ==========================

    /**
     * @brief 打开输入源（同步）
     *
     * - avformat_alloc_context()
     *   功能：分配并返回一个空的 AVFormatContext（“输入/输出会话”的句柄）。
     *   失败：返回 nullptr。
     *
     * - av_dict_set_int(&opts, "键", 值, 0) / av_dict_set(&opts, "键", "字符串值", 0)
     *   功能：向 AVDictionary 添加键值对参数；这些参数会在 avformat_open_input() 时被解析。
     *   典型键：rw_timeout/stimeout/probesize/analyzeduration/rtsp_transport/…。
     *   失败：返回负值（通常忽略；无效键会被 FFmpeg 忽略）。
     *
     * - avformat_open_input(&fmt, url, demuxer, &opts)
     *   功能：根据 url 以及 opts 打开输入源（网络握手/文件打开/创建 IO 上下文…）。
     *   成功：fmt 被填充；失败：返回负错误码，fmt 可能保持 nullptr 或半初始化。
     *
     * - avformat_find_stream_info(fmt, nullptr)
     *   功能：读取部分数据，分析容器中的流（音频/视频/字幕），并填充每个 AVStream。
     *   成功：返回非负；失败：负错误码（分析超时/无法识别）。
     */
    bool Open(const std::string &url, const media_types::NetworkOptions &net = media_types::NetworkOptions())
    {
        if (url.empty())
        {
            std::cerr << "[VideoPacketReader] URL 为空\n";
            return false;
        }
        if (url.size() > 4096)
        {
            std::cerr << "[VideoPacketReader] URL 过长 >4096\n";
            return false;
        }
        if (opened_)
            Close();

        stop_flag_.store(false);
        Cleanup(); // 防御性清理

        // —— 协议判定（便于下方按协议设置参数）
        enum class Proto
        {
            FILE,
            RTSP,
            RTMP,
            HTTP,
            HTTPS,
            UDP,
            RTP,
            OTHER
        };
        Proto proto = Proto::FILE;
        const bool is_net = url.find("://") != std::string::npos && (url.rfind("file://", 0) == std::string::npos);
        if (url.rfind("rtsp://", 0) == 0)
            proto = Proto::RTSP;
        else if (url.rfind("rtmp://", 0) == 0 || url.rfind("rtmps://", 0) == 0)
            proto = Proto::RTMP;
        else if (url.rfind("https://", 0) == 0)
            proto = Proto::HTTPS;
        else if (url.rfind("http://", 0) == 0)
            proto = Proto::HTTP;
        else if (url.rfind("udp://", 0) == 0)
            proto = Proto::UDP;
        else if (url.rfind("rtp://", 0) == 0)
            proto = Proto::RTP;
        else if (is_net)
            proto = Proto::OTHER;

        // —— 分配 AVFormatContext，并设置“中断回调”避免阻塞卡死
        fmt_ = avformat_alloc_context(); // 分配输入上下文；失败则返回 nullptr
        if (!fmt_)
        {
            std::cerr << "[VideoPacketReader] 分配 AVFormatContext 失败\n";
            return false;
        }

        // AVIO 中断回调：当 IO 阻塞时，FFmpeg 会周期性调用该回调；返回非0→中断
        fmt_->interrupt_callback.callback = &InterruptTrampoline;
        fmt_->interrupt_callback.opaque = this;

        // —— 构建 AVDictionary 选项（超时/探测/缓冲等）
        AVDictionary *opts = nullptr;

        // av_dict_set_int：向字典里写“整数型配置项”（供 open_input 读取）
        // 注：probesize 越大越稳定，首帧越慢；analyzeduration 的单位是“微秒”，因此需乘 1000。
        const int min_probe = 32 * 1024, max_probe = 8 * (1 << 20);
        const int probe = std::max(min_probe, std::min(net.probe_size_bytes, max_probe));
        av_dict_set_int(&opts, "probesize", probe, 0);

        const int min_ms = 200, max_ms = 10 * 1000;
        const int adur_ms = std::max(min_ms, std::min(net.analyze_duration_ms, max_ms));
        av_dict_set_int(&opts, "analyzeduration", (int64_t)adur_ms * 1000, 0); // μs

        if (is_net)
        {
            // rw_timeout/stimeout：控制连接/读写阻塞的超时（微秒）
            const int64_t rw_timeout_us = (int64_t)net.timeout_seconds * 1000000LL;
            if (proto == Proto::HTTP || proto == Proto::HTTPS || proto == Proto::RTMP || proto == Proto::RTSP || proto == Proto::OTHER)
            {
                av_dict_set_int(&opts, "rw_timeout", rw_timeout_us, 0);
                av_dict_set_int(&opts, "stimeout", (int64_t)net.socket_timeout_seconds * 1000000LL, 0);
            }
            if (proto == Proto::UDP || proto == Proto::RTP)
            {
                // UDP 常需更大 buffer，overrun_nonfatal=1 表示缓存溢出不致命
                av_dict_set(&opts, "buffer_size", "2097152", 0);
                av_dict_set(&opts, "fifo_size", "1000000", 0);
                av_dict_set(&opts, "overrun_nonfatal", "1", 0);
            }
            if (proto == Proto::RTSP)
            {
                // rtsp_transport 指定 TCP/UDP；prefer_tcp 有助于弱网稳定
                av_dict_set(&opts, "rtsp_transport", net.rtsp_transport.c_str(), 0);
                av_dict_set(&opts, "rtsp_flags", "prefer_tcp", 0);
                av_dict_set_int(&opts, "max_delay", (int64_t)net.max_delay_ms * 1000LL, 0);
                av_dict_set(&opts, "buffer_size", "262144", 0);
            }
            if (proto == Proto::RTMP)
            {
                av_dict_set(&opts, "buffer_size", "262144", 0);
                av_dict_set(&opts, "live", "1", 0);
            }
            if (proto == Proto::HTTP || proto == Proto::HTTPS)
            {
                av_dict_set(&opts, "buffer_size", "2097152", 0);
                av_dict_set(&opts, "user_agent", "FFmpeg/VideoPacketReader", 0);
                if (net.enable_reconnect)
                {
                    // reconnect 相关键：FFmpeg 在网络抖动时尝试自动重连
                    av_dict_set(&opts, "reconnect", "1", 0);
                    av_dict_set(&opts, "reconnect_streamed", "1", 0);
                    av_dict_set(&opts, "reconnect_delay_max", "5", 0);
                }
            }
        }
        else
        {
            // 本地文件给更大缓冲
            av_dict_set(&opts, "buffer_size", "4194304", 0);
        }

        std::cout << "[VideoPacketReader] 打开: " << url << "\n";
        // avformat_open_input：打开输入（网络握手/文件IO），读 opts 作为配置
        int ret = avformat_open_input(&fmt_, url.c_str(), nullptr, &opts);
        av_dict_free(&opts); // 用完记得释放 AVDictionary
        if (ret < 0)
        {
            PrintFFmpegError("打开失败", ret);
            Cleanup();
            return false;
        }

        // avformat_find_stream_info：读取部分数据，分析各个 AVStream
        ret = avformat_find_stream_info(fmt_, nullptr);
        if (ret < 0)
        {
            PrintFFmpegError("探测流信息失败", ret);
            Cleanup();
            return false;
        }

        // 找第一个视频流
        v_idx_ = -1;
        for (unsigned i = 0; i < fmt_->nb_streams; ++i)
        {
            if (fmt_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                v_idx_ = (int)i;
                break;
            }
        }
        if (v_idx_ < 0)
        {
            std::cerr << "[VideoPacketReader] 未找到视频流\n";
            Cleanup();
            return false;
        }

        // av_packet_alloc：分配可复用的临时包对象
        pkt_ = av_packet_alloc();
        if (!pkt_)
        {
            std::cerr << "[VideoPacketReader] 分配 AVPacket 失败\n";
            Cleanup();
            return false;
        }

        // 提取 StreamInfo（给客户端初始化解码器）
        if (!ExtractStreamInfo(url))
        {
            std::cerr << "[VideoPacketReader] 提取流信息失败\n";
            Cleanup();
            return false;
        }

        // 若 H26x 且 extradata 像 AVCC，则启用 Annex-B 过滤器。“Annex-B 过滤器”是一个比特流转换器，
        // 从 MP4/FLV 读取的 H.264/H.265（AVCC格式） 数据变成标准的 RTP/TS 可用格式。不解码，只换“包头标识方式”。
        SetupAnnexBBitstreamFilter();

        // 统计/时间戳状态
        total_pkts_ = video_pkts_ = total_bytes_ = 0;
        last_pts_ = AV_NOPTS_VALUE;
        start_ = std::chrono::steady_clock::now();
        opened_ = true;

        std::cout << "[VideoPacketReader] ✅ 打开成功\n";
        stream_info_.Print("  ");
        return true;
    }

    /**
     * @brief 读取并处理下一个容器包；仅当处理了一个“视频包”时返回 true
     *
     * 主循环逻辑：
     * 1) 读取一个容器层 AVPacket（可能来自音/字/视任一路）。
     * 2) 若为目标视频流：
     *    - 如启用 BSF，则将 H.264/H.265 的 AVCC → Annex-B；
     *    - 规范化 PTS/DTS/duration，尽量保证 PTS 单调；
     *    - 通过 PacketCallback 抛给上层（上层需在回调内自行拷贝数据以便异步使用）。
     *    - 正确释放（filtered / 原始）AVPacket 的引用。
     * 3) 若非视频包，直接释放后继续读。
     *
     * @return
     * - true  ：本次成功处理了一个“视频包”（并已回调）。
     * - false ：未能继续（EOF / 错误 / 被请求停止 / 当前包非视频而继续循环后最终退出）。
     *
     * @note
     * - 阻塞行为受 interrupt_callback 影响；stop_flag_ 置位时及时退出。
     * - pkt_ 属于复用对象，使用前后必须 av_packet_unref() 避免内存累积。
     * - 统计字段（total_pkts_/video_pkts_/total_bytes_）在此处累加。
     */
    bool ReadNextPacket()
    {
        // 未 Open 就读：直接报错返回
        if (!opened_)
        {
            std::cerr << "[VideoPacketReader] 未打开\n";
            return false;
        }

        // “读一个视频包”为止的循环；非视频包会被跳过继续读
        for (;;)
        {
            // 外部请求停止：读循环应当尽快退出
            if (stop_flag_.load())
            {
                // interrupt 回调会使阻塞 IO 尽快返回，这里二次防守
                std::cout << "[VideoPacketReader] 收到停止请求\n";
                return false;
            }

            // 读取一个“容器层”包（可能是视频/音频/字幕任意一路）
            int ret = av_read_frame(fmt_, pkt_);
            if (ret < 0)
            {
                // EOF：正常结束；其他错误：打印并上抛 ErrorCallback
                if (ret == AVERROR_EOF)
                {
                    std::cout << "[VideoPacketReader] 读取到末尾\n";
                }
                else
                {
                    PrintFFmpegError("读取失败", ret);
                    if (err_cb_)
                        err_cb_(ret, "av_read_frame 失败");
                }
                return false;
            }

            // 统计：所有容器包计数 + 累计字节
            ++total_pkts_;
            total_bytes_ += pkt_->size;

            // 命中目标视频流
            if (pkt_->stream_index == v_idx_)
            {
                ++video_pkts_;

                // ========== 1) H26x AVCC→Annex-B：如启用 BSF，则对当前包做格式转换 ==========
                AVPacket *outpkt_ptr = pkt_; // 默认输出指向原包
                AVPacket filtered_pkt;       // 过滤后的临时包
                bool filtered_ok = false;

                if (bsf_)
                {
                    // 将输入包送入过滤器
                    ret = av_bsf_send_packet(bsf_, pkt_);
                    if (ret == 0)
                    {
                        // 尝试从过滤器取出转换后的包（某些情况下可能需多取几次，这里取一次）
                        ret = av_bsf_receive_packet(bsf_, &filtered_pkt);
                        if (ret == 0)
                        {
                            filtered_ok = true;
                            outpkt_ptr = &filtered_pkt; // 后续统一使用转换后的包
                        }
                    }
                    // 注：若 ret 为 EAGAIN / EOF 等，fall back 到原包（多数情况下不会）
                }

                // ========== 2) 时间戳规范化：补齐 pts/dts/duration，并尽量保持 PTS 单调 ==========
                NormalizeTimestamps(*outpkt_ptr, fmt_->streams[v_idx_]);

                // ========== 3) 回调：把当前“可用视频包”抛给上层 ==========
                // 重要：outpkt_ptr 的生命周期仅在回调期间有效；
                // 若上层要跨线程使用，需在回调内做深拷贝或 av_packet_ref。
                if (pkt_cb_)
                {
                    try
                    {
                        pkt_cb_(outpkt_ptr, stream_info_);
                    }
                    catch (const std::exception &e)
                    {
                        std::cerr << "[VideoPacketReader] 回调异常: " << e.what() << "\n";
                    }
                    catch (...)
                    {
                        std::cerr << "[VideoPacketReader] 回调未知异常\n";
                    }
                }

                // ========== 4) 释放引用：先释放 filtered，再释放原始包（或仅释放原始包） ==========
                if (filtered_ok)
                {
                    av_packet_unref(&filtered_pkt); // 释放过滤后包的引用
                    av_packet_unref(pkt_);          // 同时释放原始输入包
                }
                else
                {
                    av_packet_unref(pkt_); // 无过滤时，仅释放原始输入包
                }

                // 本轮读到并处理了一个“视频包”，返回 true
                return true;
            }

            // 非目标流（音频/字幕等）：释放后进入下一轮读取
            av_packet_unref(pkt_); // 减少 pkt_ 指向的数据缓冲区的引用计数
        }
    }

    /**
     * @brief 关闭读取器（幂等；安全多次调用）
     *
     * 行为：
     * - 调用 Cleanup() 释放 FFmpeg 上下文、包缓冲、过滤器等资源。
     * - 将 opened_ 复位为 false，并输出关闭日志。
     *
     * @note
     * - 若对象处于打开状态，建议先确保外部停止读循环（调用 RequestStop()/设置 stop_flag_），
     *   再调用 Close()；否则阻塞中的读可能尚未退出。
     * - Close() 可被析构函数安全重复调用（幂等）。
     */
    void Close()
    {
        if (!opened_)
        {
            // 即便未打开，也做一次 Cleanup() 以保证资源处于干净状态
            Cleanup();
            return;
        }

        std::cout << "[VideoPacketReader] 关闭...\n";

        // 统一释放：格式上下文 / AVPacket / 比特流过滤器 / 统计复位 等
        Cleanup();

        // 标记为未打开状态
        opened_ = false;

        std::cout << "[VideoPacketReader] 已关闭\n";
    }

    /// 请求尽快停止正在阻塞的 IO（线程安全；中断由 interrupt 回调实现）
    void RequestStop() { stop_flag_.store(true); }

    //========================== 访问器/统计 ==========================

    //========================== 访问器 / 统计 ==========================

    /**
     * @brief 设置视频包回调函数（PacketCallback）当 Reader 成功读取并处理一个视频包时，会调用该回调。
     *
     * @param cb
     *     用户提供的回调函数对象；若为空则禁用回调。
     *
     * @note
     * - 回调在 Reader 的读取线程中触发，应避免执行耗时操作。函数使用 std::move() 以支持传入临时 lambda。
     */
    void SetPacketCallback(PacketCallback cb) { pkt_cb_ = std::move(cb); }

    /**
     * @brief 设置错误回调函数（ErrorCallback）当 Reader 发生 I/O 错误、解复用错误或到达 EOF 时，会触发该回调。
     *
     * @param cb
     *     用户提供的错误回调函数对象。
     */
    void SetErrorCallback(ErrorCallback cb) { err_cb_ = std::move(cb); }

    /**
     * @brief 判断 Reader 当前是否已成功打开媒体源
     *
     * @return
     *     true  - 已打开且可读取；
     *     false - 尚未打开或已关闭。
     */
    bool IsOpened() const { return opened_; }

    /**
     * @brief 获取当前视频流的元信息（StreamInfo）
     *
     * @return
     *     对当前流信息结构的常量引用。
     *
     * @note
     * - StreamInfo 在打开媒体源时由 ExtractStreamInfo() 填充；
     * - 包含 codec_id、分辨率、帧率、比特率、extradata 等；
     * - 返回的是常量引用，外部仅可读取，不可修改。
     */
    const media_types::StreamInfo &GetStreamInfo() const { return stream_info_; }

    /**
     * @brief 获取当前读取统计信息（累计包数、速率、吞吐等）
     *
     * 汇总自 Reader 启动至今的实时统计：
     * - total_packets ：读到的容器包总数（含音频/字幕）
     * - video_packets ：其中视频包数量
     * - total_bytes   ：累计读取字节数
     * - elapsed_seconds ：耗时（秒）
     * - packets_per_second ：平均视频包读取速率（pkt/s）
     * - mbps ：平均吞吐量（Mbit/s）
     *
     * @return
     *     一个独立的 Statistics 结构体实例（值拷贝）。
     *
     * @note
     * - 本函数为线程安全读操作，不修改内部状态。
     * - 速率与吞吐计算基于 steady_clock，避免系统时钟跳变影响。
     */
    media_types::Statistics GetStatistics() const
    {
        media_types::Statistics s{};

        // 累计计数数据
        s.total_packets = total_pkts_;
        s.video_packets = video_pkts_;
        s.total_bytes = total_bytes_;

        // 计算耗时（秒）
        auto now = std::chrono::steady_clock::now();
        s.elapsed_seconds = std::chrono::duration<double>(now - start_).count();

        // 若时间有效，则计算速率与吞吐
        if (s.elapsed_seconds > 0)
        {
            // 每秒读取视频包数
            s.packets_per_second = static_cast<double>(s.video_packets) / s.elapsed_seconds;

            // 吞吐量：bit/s → Mbit/s
            s.mbps = (s.total_bytes * 8.0 / 1e6) / s.elapsed_seconds;
        }

        return s;
    }

    /**
     * @brief 打印当前统计信息到标准输出
     *
     * 内部调用 GetStatistics() 获取最新数据，
     * 再调用 Statistics::Print() 输出。
     *
     * @note
     * - 默认使用两个空格前缀，便于在日志中区分层级。
     * - 输出内容包括包数、总字节、速率、吞吐等指标。
     */
    void PrintStatistics() const { GetStatistics().Print("  "); }

private:
    //========================== 内部工具 ==========================

    /// AVIO 的中断回调：返回非 0 表示应中断（避免长时间阻塞）
    static int InterruptTrampoline(void *opaque)
    {
        auto *self = static_cast<VideoPacketReader *>(opaque);
        return self->stop_flag_.load() ? 1 : 0;
    }

    /// 释放 FFmpeg 资源：pkt/bsf/fmt（注意释放顺序与幂等）
    void Cleanup()
    {
        if (pkt_)
        {
            av_packet_free(&pkt_);
            pkt_ = nullptr;
        } // 释放包对象
        if (bsf_)
        {
            av_bsf_free(&bsf_);
            bsf_ = nullptr;
        } // 释放比特流过滤器
        if (fmt_)
        {
            avformat_close_input(&fmt_);
            fmt_ = nullptr;
        } // 关闭输入：释放 AVFormatContext 与底层 IO
        v_idx_ = -1;
        last_pts_ = AV_NOPTS_VALUE;
    }

    /**
     * @brief 从 FFmpeg 的 AVStream 中提取视频流的关键信息
     *
     * 本函数在成功打开媒体输入（avformat_open_input + avformat_find_stream_info）
     * 并确定视频流索引 v_idx_ 之后调用。它从 FFmpeg 的 AVStream 与 AVCodecParameters 中读取
     * 编码参数（codec_id、宽高、时间基、比特率、SPS/PPS等），并填充到内部的 stream_info_ 结构体中，供上层使用。
     *
     * @param url
     *     当前输入媒体源的 URL（如 RTSP 地址、文件路径等）。
     *
     * @return
     *     始终返回 true，表示提取流程执行完毕；
     *     若部分字段缺失，则填入默认值或 "unknown"。
     *
     * @note
     * - 本函数不打开解码器，仅通过 codec_id 获取解码器描述信息。若 AVStream 中缺失部分参数（如 nb_frames、extradata），对应字段将为默认值。
     * - 提取的结果存放在成员变量 stream_info_ 中。
     */
    bool ExtractStreamInfo(const std::string &url)
    {
        // 获取视频流指针：fmt_->streams[v_idx_] 是已选中的视频流
        AVStream *st = fmt_->streams[v_idx_];

        // AVCodecParameters：流的编解码参数结构，包含编码格式、宽高、比特率等
        AVCodecParameters *cp = st->codecpar;

        // 根据 codec_id 查找解码器“描述体”，不是实例化解码器，只用于获取 codec 名称
        const AVCodec *codec = avcodec_find_decoder(cp->codec_id);

        // 重置结构体，确保干净的初始状态
        stream_info_ = {};

        // 填写基础信息：URL 和视频流索引
        stream_info_.url = url;
        stream_info_.stream_index = v_idx_;

        // 提取容器报告的时长（单位：秒）
        // fmt_->duration 以 AV_TIME_BASE（1秒=1000000）为基准；若未知则为 AV_NOPTS_VALUE
        if (fmt_->duration != AV_NOPTS_VALUE)
            stream_info_.duration = fmt_->duration / (double)AV_TIME_BASE;

        // 提取总帧数（有的容器会在 header 中报告总帧数，否则填 -1 表示未知）
        stream_info_.total_frames = (st->nb_frames > 0) ? (int64_t)st->nb_frames : -1;

        //===================== 编解码器描述 =====================//

        // 编解码器 ID（枚举类型 AVCodecID），客户端可据此初始化对应解码器
        stream_info_.codec_id = cp->codec_id;

        // 编解码器短名，如 "h264"、"hevc"；若找不到解码器则标记为 "unknown"
        stream_info_.codec_name = codec && codec->name ? codec->name : "unknown";

        // 编解码器长名，如 "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"
        stream_info_.codec_long_name = codec && codec->long_name ? codec->long_name : "Unknown";

        //===================== 附加参数（extradata） =====================//

        // H.264 / H.265 等编解码器会在 extradata 中包含 SPS/PPS/VPS 等配置
        // 若存在，复制到 stream_info_.extradata 中
        if (cp->extradata_size > 0 && cp->extradata)
            stream_info_.extradata.assign(cp->extradata, cp->extradata + cp->extradata_size);

        //===================== 视频尺寸与像素格式 =====================//

        // 视频分辨率
        stream_info_.width = cp->width;
        stream_info_.height = cp->height;

        // 像素格式（AVPixelFormat 枚举值）；某些封装格式可能填 -1（未知）
        stream_info_.pix_fmt = (AVPixelFormat)cp->format;

        // 获取像素格式名称（如 yuv420p、nv12）
        const char *pfn = av_get_pix_fmt_name(stream_info_.pix_fmt);
        stream_info_.pix_fmt_name = pfn ? pfn : "";

        //===================== 时间基与帧率 =====================//

        // 时间基（每个 PTS 单位对应的时间长度）
        // 例如 time_base = {1, 90000} 表示 1单位=1/90000秒
        stream_info_.time_base = st->time_base;

        // 平均帧率（AVRational 结构），由容器或编码器标注
        stream_info_.frame_rate = st->avg_frame_rate;

        // 将帧率换算为 double 形式（fps），例如 25.0、29.97 等
        stream_info_.fps = av_q2d(st->avg_frame_rate);

        // 比特率（单位：bit/s），若未知则为 0
        stream_info_.bit_rate = cp->bit_rate;

        // 一切就绪，返回成功
        return true;
    }

    /**
     * @brief 设置 Annex-B 比特流过滤器（AVCC → Annex-B 格式转换）
     *
     * 某些容器（如 MP4、FLV、MOV）中的 H.264/H.265 视频流采用 AVCC 格式：
     * 每个 NALU 前 4 字节表示长度，而非 Annex-B 的起始码 (00 00 00 01)。
     * 解码器和实时推流通常要求 Annex-B 格式，因此需通过 FFmpeg 的比特流过滤器（Bitstream Filter, BSF）进行转换。
     *
     * 本函数检测当前视频流的编码格式与 extradata 特征，若为 H.264/H.265 且为 AVCC 格式，则创建并初始化对应的 BSF：
     * - H.264 → `h264_mp4toannexb`
     * - H.265 → `hevc_mp4toannexb`
     *
     * 转换完成后，后续在读取每个 AVPacket 时调用 av_bsf_send_packet / av_bsf_receive_packet，即可输出 Annex-B 格式的码流。
     *
     * @note
     * 若流本身已是 Annex-B（例如 RTSP/TS），则此函数直接返回不做处理。若过滤器创建失败，会释放已分配资源并禁用 bsf_。
     * 仅对视频流生效；音频流无需此步骤。
     */
    void SetupAnnexBBitstreamFilter()
    {
        // 检查前提条件：必须已有打开的格式上下文 fmt_ 且选中的视频流索引有效
        if (!fmt_ || v_idx_ < 0)
            return;

        // 获取视频流的编解码参数结构
        const AVCodecParameters *cp = fmt_->streams[v_idx_]->codecpar;

        // 判断视频编码类型是否为 H.264 或 H.265（HEVC）
        const bool is_h264 = (cp->codec_id == AV_CODEC_ID_H264);
        const bool is_h265 = (cp->codec_id == AV_CODEC_ID_HEVC);

        //========================== 判断流格式类型 ==========================//
        // 经验性判断：若 extradata[0] == 1，则多为 AVCC 格式（mp4/flv 常见）
        // Annex-B 格式的 extradata 通常以 0x00 0x00 开头
        bool looks_avcc = false;
        if (cp->extradata && cp->extradata_size > 0)
            looks_avcc = (cp->extradata[0] == 1);

        // 若不是 H.264/H.265，或不是 AVCC 格式，则无需过滤器
        if (!(is_h264 || is_h265) || !looks_avcc)
            return;

        //========================== 获取过滤器定义 ==========================//
        // 根据编码类型选择对应的比特流过滤器名称
        const char *name = is_h264 ? "h264_mp4toannexb" : "hevc_mp4toannexb";

        // 按名称从 FFmpeg 注册表中获取过滤器描述体
        const AVBitStreamFilter *filter = av_bsf_get_by_name(name);
        if (!filter)
            return; // 如果系统中未注册该过滤器（极少见），则直接退出

        //========================== 创建并初始化过滤器 ==========================//
        // av_bsf_alloc：分配过滤器上下文（AVBSFContext）
        if (av_bsf_alloc(filter, &bsf_) == 0 && bsf_)
        {
            // 拷贝输入流的编解码参数到过滤器的输入参数区
            // 确保过滤器知道输入数据的编码格式和附加信息
            if (avcodec_parameters_copy(bsf_->par_in, cp) == 0)
            {
                // 设置时间基（time_base_in）：用于保持时间戳同步
                bsf_->time_base_in = fmt_->streams[v_idx_]->time_base;

                // 初始化过滤器（内部会分配缓冲区并解析 extradata）
                if (av_bsf_init(bsf_) == 0)
                {
                    std::cout << "[VideoPacketReader] 启用 BSF: " << name
                              << "（AVCC→Annex-B）\n";
                    return; // 成功启用，函数结束
                }
            }
        }

        //========================== 错误回退处理 ==========================//
        // 若任一步失败，则释放已分配的过滤器上下文并清空指针
        if (bsf_)
        {
            av_bsf_free(&bsf_);
            bsf_ = nullptr;
        }
    }

    /**
     * @brief 规范化 AVPacket 时间戳：补齐 pts/dts/duration，并尽量保持 PTS 单调
     *
     * 该函数以指定流的 time_base 为单位，对输入的 AVPacket 时间戳进行修正：
     * 1) 若 PTS/DTS 其一缺失，则用另一方补齐（常见于部分封装/实时源）。
     * 2) 若 duration 缺失，尝试基于 avg_frame_rate 估算一帧时长（恒定帧率更可靠）。
     * 3) 为防时间回拨（reorder/乱序），在可用时对 PTS 做单调性校正：
     *    若新 PTS <= 上次 PTS，则推进到 last_pts_ + 1（同样以 time_base 为单位）。
     *
     * @param[in,out] pkt
     *     待修正的压缩包（AVPacket）。函数可能会改写 pkt.pts / pkt.dts / pkt.duration。
     * @param[in]     st
     *     对应的 AVStream 指针；用于获取 time_base 与 avg_frame_rate。
     *
     * @note
     * - 所有时间均以 st->time_base 为单位（即“时钟刻度”）。
     * - duration 估算使用 avg_frame_rate，若为变帧率（VFR）或源端标注不准，则仅作保守近似。
     * - 单调性修正只对 PTS 生效；DTS 维持“解码顺序时间戳”语义不强行单调。
     * - last_pts_ 为类成员，记录上一帧“已提交”的 PTS；需在打开流/切换流时复位为 AV_NOPTS_VALUE。
     */
    void NormalizeTimestamps(AVPacket &pkt, AVStream *st)
    {
        // 缓存原始值，避免在补齐逻辑中相互覆盖
        int64_t pts = pkt.pts;
        int64_t dts = pkt.dts;

        //========================= 补齐缺失的 PTS/DTS =========================//
        // 情况A：只有 DTS → 用 DTS 填 PTS（很多容器只给 DTS）
        if (pts == AV_NOPTS_VALUE && dts != AV_NOPTS_VALUE)
            pts = dts;

        // 情况B：只有 PTS → 用 PTS 填 DTS（无B帧或源端未提供DTS）
        if (dts == AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE)
            dts = pts;

        //========================= 估算缺失的 duration =========================//
        // 若包时长缺失且存在平均帧率，则把“1帧”从秒表换算到 time_base 刻度：
        // dur = av_rescale_q(1, 1/fps, time_base)
        if (pkt.duration == 0 && st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0)
        {
            // av_inv_q(r) 返回其倒数；avg_frame_rate = 帧/秒 → 1/fps = 秒/帧
            const int64_t dur = av_rescale_q(1, av_inv_q(st->avg_frame_rate), st->time_base);
            if (dur > 0)
                pkt.duration = static_cast<int>(dur);
        }

        //========================= 保证 PTS 尽量单调 =========================//
        // 某些源（含B帧或乱序）会出现 pts 回退；为了下游同步与渲染的稳定性，
        // 若检测到新 pts <= 上次 pts，则把它“微调”到 last_pts_ + 1（最小递增步长）。
        if (pts != AV_NOPTS_VALUE)
        {
            if (last_pts_ != AV_NOPTS_VALUE && pts <= last_pts_)
            {
                pts = last_pts_ + 1; // 单位：st->time_base 的“刻度”
            }
            last_pts_ = pts; // 更新“上一帧提交的 PTS”
        }

        //========================= 回写修正后的时间戳 =========================//
        pkt.pts = pts;
        pkt.dts = dts;
    }

    /// 统一打印 FFmpeg 错误码
    void PrintFFmpegError(const std::string &msg, int err)
    {
        char buf[AV_ERROR_MAX_STRING_SIZE]{0};
        av_strerror(err, buf, sizeof(buf));
        std::cerr << "[VideoPacketReader] ❌ " << msg << ": " << buf << " (code=" << err << ")\n";
    }

private:
    //========================== 成员变量 ==========================

    AVFormatContext *fmt_; ///< 输入上下文（容器/IO 的“会话句柄”）
    AVPacket *pkt_;        ///< 循环复用的临时 AVPacket（每次 Read 填充，回调后 unref）
    int v_idx_;            ///< 选中的视频流索引；无则 -1
    AVBSFContext *bsf_;    ///< H26x AVCC→Annex-B 的比特流过滤器；未启用时为 nullptr

    bool opened_;
    int64_t total_pkts_;
    int64_t video_pkts_;
    int64_t total_bytes_;
    std::chrono::steady_clock::time_point start_;

    int64_t last_pts_;            ///< 上一个视频包的 pts（time_base 单位）；用于“尽量单调”
    std::atomic<bool> stop_flag_; ///< 外部停止标志（中断阻塞 IO）

    media_types::StreamInfo stream_info_; ///< 打开后固定的流信息（发给客户端）
    PacketCallback pkt_cb_;  ///< 视频包回调
    ErrorCallback err_cb_;   ///< 错误回调
};

#endif // VIDEO_PACKET_READER_HPP
